package com.sojson.aspect;

import static com.sojson.util.IdempoterUtil.CACHE_IDEMPOTENT_KEY;
import static com.sojson.util.IdempoterUtil.CACHE_IDEMPOTENT_VALUE;
import static com.sojson.util.IdempoterUtil.IDEMPOTENT;
import static com.sojson.util.IdempoterUtil.IDEMPOTENT_OBJ_KEY;
import static com.sojson.util.IdempoterUtil.IDEMPOTENT_PERMIT;
import static com.sojson.util.IdempoterUtil.LEFT;
import static com.sojson.util.IdempoterUtil.REIGHT;
import static com.sojson.util.IdempoterUtil.WAIT_TIME;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;

import com.sojson.util.ExpUtil;
import com.sojson.util.IdempoterUtil;
import com.sojson.util.cache.CacheUtil;
import com.sojson.util.encrypt.oneway.alike.md5.Md5Util;
import com.sojson.util.json.JsonUtil;
import com.sojson.util.param.ParamUtil;
import com.sojson.util.servlet.ServletUtil;
import com.sojson.util.token.Token;
import com.sojson.util.token.TokenUtil;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 幂等切面
 * 
 * @author liu
 * @date 2022-01-21
 */
@Aspect
@Order(-1)
@Component
public class IdempoterAspect implements PointcutsWebBase {

    @Around(POINTCUT_CONTROLLER)
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = ServletUtil.getHttpServletRequest();

        // 前端传的幂等参数
        String clientIdempotent = request.getHeader(IDEMPOTENT);
        // 若不考虑幂等,则直接允许
        if (IDEMPOTENT_PERMIT.equals(clientIdempotent)) {
            return pjp.proceed();
        }

        StringBuilder idempotentKeySb = new StringBuilder();
        // 获取URI
        String requestURI = request.getRequestURI();
        // 获取客户端IP
        String clientIP = ServletUtil.getTargetIp();
        // 拼装幂等hash字符串
        if (StrUtil.isNotBlank(clientIdempotent)) {
            idempotentKeySb.append(LEFT);
            idempotentKeySb.append(clientIdempotent);
            idempotentKeySb.append(REIGHT);
        }
        idempotentKeySb.append(LEFT);
        idempotentKeySb.append(clientIP);
        idempotentKeySb.append(REIGHT);
        idempotentKeySb.append(LEFT);
        idempotentKeySb.append(requestURI);
        idempotentKeySb.append(REIGHT);
        if (ParamUtil.isLogin()) {
            idempotentKeySb.append(LEFT);
            idempotentKeySb.append(TokenUtil.getUserId());
            idempotentKeySb.append(REIGHT);
        }
        boolean breakBool = IdempoterUtil.breakBool();
        Object[] args = pjp.getArgs();
        if (ArrayUtil.isNotEmpty(args) && pjp.getSignature() instanceof MethodSignature) {
            // 获取方法
            MethodSignature signature = (MethodSignature)pjp.getSignature();
            Method method = signature.getMethod();
            // 获取入参的注解
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            List<Object> argList = new ArrayList<>();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                Object param = args[i];
                Annotation[] parameterAnnotation = parameterAnnotations[i];
                // 参数为空,或者参数上没有注解,则跳出循环
                // if (ObjectUtil.isNull(param) || parameterAnnotation.length == 0) {
                // continue;
                // }
                // 只有入参被RequestBody注解时才会当做序列化参数保存
                // for (Annotation annotation : parameterAnnotation) {
                // if (annotation.annotationType().equals(RequestBody.class)
                // || annotation.annotationType().equals(PathVariable.class)) {
                // argList.add(param);
                // break;
                // }
                // }
                // 参数为空,则跳出循环
                if (param == null) {
                    continue;
                }
                if (param instanceof MultipartFile) {
                    argList.add(((MultipartFile)param).getOriginalFilename());
                } else if (parameterAnnotation.length == 0) {
                    argList.add(param);
                } else {
                    for (Annotation annotation : parameterAnnotation) {
                        if (annotation.annotationType().equals(RequestBody.class)
                            || annotation.annotationType().equals(PathVariable.class)) {
                            argList.add(param);
                            break;
                        }
                    }
                }
            }
            if (CollUtil.isNotEmpty(argList)) {
                String argJson = JsonUtil.objectToJsonAll(argList);
                idempotentKeySb.append(argJson);
            }
        }

        // 对幂等字符串做md5 hash 获取redis的key
        String idempotentKey = CACHE_IDEMPOTENT_KEY + ":" + Md5Util.encrypt(idempotentKeySb.toString());
        // 拼装即将需要存放返回值的key s
        String idempotentObjKey = CACHE_IDEMPOTENT_KEY + "_OBJ:" + idempotentKey;
        request.setAttribute(IDEMPOTENT_OBJ_KEY, idempotentObjKey);
        // 设置key,若不存在该key,则返回true,否则返回false
        boolean exists = CacheUtil.exists(idempotentKey);
        // key存在的话,则表示幂等
        if (!breakBool && exists) {
            Object requestObj = CacheUtil.get(idempotentObjKey);
            // 触发幂等,直接返回
            if (ObjectUtil.isNotNull(requestObj)) {
                return requestObj;
            }
            // 若未获取到返回值,可能存在上个请求正在运行
            // 重复触发接口
            String errMsg = "用户{},重复请求:{}";
            Token token = TokenUtil.getToken();
            if (ObjectUtil.isNotNull(token)) {
                errMsg = StrFormatter.format(errMsg, token.getUser().getUsername(), requestURI);
            } else {
                errMsg = StrFormatter.format(errMsg, "未登录", requestURI);
            }
            ExpUtil.throwEx(errMsg);
        }

        // 若返回数据正常,则保存
        Object proceed = null;
        CacheUtil.setex(idempotentKey, CACHE_IDEMPOTENT_VALUE, WAIT_TIME);
        proceed = pjp.proceed();
        IdempoterUtil.save(request, proceed);
        return proceed;
    }

}